/***************************************************************************
 *  AudioCdRipper.cs
 *
 *  Copyright (C) 2005-2006 Novell, Inc.
 *  Written by Aaron Bockover <aaron@abock.org>
 ****************************************************************************/

/*  THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW: 
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),  
 *  to deal in the Software without restriction, including without limitation  
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,  
 *  and/or sell copies of the Software, and to permit persons to whom the  
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */
 
using System;
using System.IO;
using System.Collections;
using System.Threading;
using Banshee.Winforms;
using Banshee.Winforms.Controls;
using Banshee.Widgets;
using Banshee.Base;
using Banshee.AudioProfiles;
using Banshee.Configuration.Schema;
using Ripper;
using Yeti.MMedia;
using Yeti.MMedia.Mp3;
using WaveLib;
 
        //Cornel - I have decided  not to use the WMP SDK for ripping
        //          although it does support ripping etc the files get added to the wmp media library 
        //          i would like to stick to the banshee convention of allowing the user to specify the 
        //          location of the banshee media library. Using lame_enc.dll seems like a good option
        //          To start with only mp3 is supported    
namespace Banshee.Base
{
    public delegate void AudioCdRipperProgressHandler(object o, AudioCdRipperProgressArgs args);
    public delegate void AudioCdRipperTrackFinishedHandler(object o, AudioCdRipperTrackFinishedArgs args);
        
    public class AudioCdRipperProgressArgs : EventArgs
    {
        public int SecondsEncoded;
        public int TotalSeconds;
        public AudioCdTrackInfo Track;
    }

    public class AudioCdRipperTrackFinishedArgs : EventArgs
    {
        public AudioCdTrackInfo Track;
        public int TrackNumber;
        public SafeUri Uri;
    }

    public class AudioCdTrackRipper : IDisposable
    {

        private string error_message;
        private SafeUri output_uri;
        private int track_number;

        private AudioCdTrackInfo current_track;
        
        public event AudioCdRipperProgressHandler Progress;
        public event AudioCdRipperTrackFinishedHandler TrackFinished;
        public event EventHandler Error;
        CDDrive drive;
        Mp3Writer writer;

        public AudioCdTrackRipper(CDDrive drv, int paranoiaMode, string encoderPipeline)
        {
            drive = drv;
        }
        
        public void Dispose()
        {
            drive.Dispose();
        }
        
        public void RipTrack(AudioCdTrackInfo track, int trackNumber, SafeUri outputUri)
        {
            error_message = null;
            RipTrack_010(track, trackNumber, outputUri);
        }
  
        private void RipTrack_010(AudioCdTrackInfo track, int trackNumber, SafeUri outputUri)
        {
            current_track = track;
            track_number = trackNumber;
            output_uri = outputUri;
            Stream WaveFile=null;
            try
            {
                WaveFormat Format = new WaveFormat(44100, 16, 2);
                WaveFile = new FileStream(outputUri.AbsolutePath, FileMode.Create, FileAccess.Write);
                try
                {
                    writer = new Mp3Writer(WaveFile, Format);
                    int n = drive.ReadTrack(track_number, new CdDataReadEventHandler(WriteWaveData), new CdReadProgressEventHandler(OnProgress));
                    if (n > 0)
                    {
                        LogCore.Instance.PushInformation("CD Ripping", string.Format("Ripping track {0}",track_number));
                    }
                    else
                    {
                        writer.Close();
                        writer = null;
                        WaveFile.Close();
                        if(System.IO.File.Exists(outputUri.AbsolutePath))
                            System.IO.File.Delete(outputUri.AbsolutePath);
                        LogCore.Instance.PushError("CD Ripping", string.Format(" Error Ripping track {0}",track_number));
                    }
                }
                finally
                {
                    writer.Close();
                    writer = null;
                }
            }
            catch (Exception ex)
            {
                error_message = ex.Message;
            }
            finally
            {
                WaveFile.Close();
                drive.UnLockCD();
                OnTrackFinished(current_track, (int)current_track.TrackNumber, outputUri);
            }
            track = null;
        }
        private void WriteWaveData(object sender, DataReadEventArgs ea)
        {
            if (writer != null)
            {
                writer.Write(ea.Data, 0, (int)ea.DataSize);
            }
        }
        private void OnTrackFinished(AudioCdTrackInfo track, int trackNumber, SafeUri outputUri)
        {
            track.IsRipped = true;
            track.Uri = outputUri;
            
            AudioCdRipperTrackFinishedHandler handler = TrackFinished;
            if(handler != null) {
                AudioCdRipperTrackFinishedArgs args = new AudioCdRipperTrackFinishedArgs();
                args.Track = track;
                args.TrackNumber = trackNumber;
                args.Uri = outputUri;
                handler(this, args);
            }
        }
        
        public void Cancel()
        {
            //gst_cd_ripper_cancel(handle);
        }
        
        public string ErrorMessage {
            get {
                return error_message;
            }
        }
        private void OnProgress(object sender, ReadProgressEventArgs ea)
        {
            ulong Percent = ((ulong)ea.BytesRead * 100) / ea.Bytes2Read;
            int dur = (int)current_track.Duration.TotalSeconds;
            int secondsread = (dur / 100) * (int)Percent;
            System.Windows.Forms.Application.DoEvents();
            AudioCdRipperProgressHandler handler = Progress;
            if (handler == null)
                return;
            AudioCdRipperProgressArgs args = new AudioCdRipperProgressArgs();
            args.TotalSeconds = (int)current_track.Duration.TotalSeconds;
            args.SecondsEncoded = secondsread;
            args.Track = current_track;

            handler(this, args); 
        }
/*        private void OnProgress(object ripper, int seconds, IntPtr user_info)
        {


        }
 */
        private void OnFinished(IntPtr ripper)
        {
            OnTrackFinished(current_track, track_number, output_uri);
        }
        
        private void OnError(IntPtr ripper, IntPtr error, IntPtr debug)
        {
            error_message = error.ToString();
            
            if(debug != IntPtr.Zero) {
                string debug_string = debug.ToString();
                if(debug_string != null && debug_string != String.Empty) {
                    error_message += ": " + debug_string;
                }
            }
            
            if(Error != null) {
                Error(this, new EventArgs());
            }
        }
    }

    public class AudioCdRipper
    {
        private Queue tracks = new Queue();
        private AudioCdTrackRipper ripper;
        private string device;
        private int currentIndex = 0;
        private int overallProgress = 0;
        private string status;
        private int total_tracks;
        
        // speed calculations
        private int currentSeconds = 0;
        private int lastPollSeconds = 0;
        private uint pollDelay = 1000;
        private long totalCount;
        
        private uint timeout_id; 
        private int current = 1;
        private Profile profile;
        
        public event HaveTrackInfoHandler HaveTrackInfo;
        public event EventHandler Finished;
        
        private ActiveUserEvent user_event;
        private void LockDrive()
        {
            lock(this) {
                bool t = drive.UnLockCD();
                if(!drive.LockCD()) {
                    LogCore.Instance.PushWarning("Could not lock CD-ROM drive", device, false);
                }
            }
        }
        
        private void UnlockDrive()
        {
            lock(this) {
                if(!drive.UnLockCD()) {
                    LogCore.Instance.PushWarning("Could not unlock CD-ROM drive", device, false);
                }
            }
        }
        CDDrive drive = new CDDrive();
        char devchar;
        public AudioCdRipper(string DriveLetter)
        {
            devchar = DriveLetter[0];

            user_event = new ActiveUserEvent(Catalog.GetString("Importing CD"));
            
            
            user_event.Icon = IconThemeUtils.LoadIcon(ConfigureDefines.ICON_THEME_DIR+@"\cd-action-rip-24.png");
            user_event.CancelRequested += OnCancelRequested;

        }

        public void QueueTrack(AudioCdTrackInfo track)
        {
            if(user_event.CancelMessage == null) {
                user_event.CancelMessage = String.Format(Catalog.GetString(
                    "{0} is still being imported into the music library. Would you like to stop it?"
                ), track.Album);
            }
            
            if(device == null) {
                device = track.Device;
            } else if(device != track.Device) {
                throw new ApplicationException(String.Format(Catalog.GetString(
                    "The device node '{0}' differs from the device node " + 
                    "already set for previously queued tracks ({1})"),
                    track.Device, device));
            }

            tracks.Enqueue(track);
            totalCount += (int)track.Duration.TotalSeconds;
        }
        
        public void Start()
        {
            //ActiveUserEventsManager.Register(user_event);
            current = 1;
            user_event.BeginInvoke((System.Windows.Forms.MethodInvoker)delegate()
            {
                user_event.Header = Catalog.GetString("Importing Audio CD");
                user_event.OnUpdateStatus();
                user_event.Message = Catalog.GetString("Initializing Drive");
                user_event.OnUpdateStatus();
                drive.Open(devchar);

                if (drive.IsCDReady())
                {
                    bool refreshed = drive.Refresh();
                    user_event.Message = Catalog.GetString("Initializing Drive");
                    user_event.OnUpdateStatus();
                }

                /* profile = Globals.AudioProfileManager.GetConfiguredActiveProfile("cd-importing", 
                     new string [] { "audio/ogg", "audio/mp3", "audio/wav" });
            
                 try {
                     if(profile == null) {
                         throw new ApplicationException(Catalog.GetString("No encoder was found on your system."));
                     }
            
                     string encodePipeline = profile.Pipeline.GetProcessById("gstreamer");
                 */
                LogCore.Instance.PushDebug("Ripping CD and Encoding with Pipeline: ", "lame");

                try
                {
                    //LockDrive();
                    bool locked = drive.LockCD();
                    int paranoiaMode = 0;

                    try
                    {
                        if (ImportSchema.AudioCDErrorCorrection.Get())
                        {
                            paranoiaMode = 255;
                            LogCore.Instance.PushDebug("CD Error-correction enabled", "using full paranoia mode (255)");
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }
                    ripper = new AudioCdTrackRipper(drive, paranoiaMode, string.Empty);//encodePipeline);
                    ripper.Progress += OnRipperProgress;
                    ripper.TrackFinished += OnTrackRipped;
                    ripper.Error += OnRipperError;

                    //timeout_id = GLib.Timeout.Add(pollDelay, OnTimeout);

                    total_tracks = tracks.Count;

                    RipNextTrack();
                }
                catch (Exception e)
                {
                    LogCore.Instance.PushError(Catalog.GetString("Cannot Import CD"), e.Message);

                    //user_event.Dispose();
                    OnFinished();
                }
            });
        }
        
        public void Cancel()
        {
            user_event.Dispose();
            OnFinished();
        }
        
        private void RipNextTrack()
        {
            if(tracks.Count <= 0) {
                return;
            }
            
            AudioCdTrackInfo track = tracks.Dequeue() as AudioCdTrackInfo;

            user_event.Header = String.Format(Catalog.GetString("Importing {0} of {1}"), current++, QueueSize);
            user_event.OnUpdateStatus();
            status = String.Format("{0} - {1}", track.Artist, track.Title);
            user_event.Message = status;
            Console.WriteLine("Importing {0} of {1}", current++, QueueSize);
            SafeUri uri = new SafeUri(FileNamePattern.BuildFull(track, "mp3"));//profile.OutputFileExtension));
            user_event.OnUpdateStatus();
            ripper.RipTrack(track, track.TrackIndex, uri);
        }
        
        private void OnTrackRipped(object o, AudioCdRipperTrackFinishedArgs args)
        {
            overallProgress += (int)args.Track.Duration.TotalSeconds;

            if(!user_event.IsCancelRequested) {
                TrackInfo lti;
                try {
                    lti = new LibraryTrackInfo(args.Uri, args.Track);
                } catch(ApplicationException) {
                    lti = Globals.Library.TracksFnKeyed[Library.MakeFilenameKey(args.Uri)] as TrackInfo;
                }
            
                if(lti != null) {                       
                    HaveTrackInfoHandler handler = HaveTrackInfo;
                    if(handler != null) {
                        HaveTrackInfoArgs hargs = new HaveTrackInfoArgs();
                        hargs.TrackInfo = lti;
                        handler(this, hargs);
                    }
                }
            }

            currentIndex++;
            
            if(tracks.Count > 0 && !user_event.IsCancelRequested) {
                RipNextTrack();
                return;
            }
            
            if(timeout_id > 0) {
               // GLib.Source.Remove(timeout_id);
            }
                
            ripper.Dispose();
            user_event.Dispose();
            OnFinished();
        }
        
        private void OnRipperError(object o, EventArgs args)
        {
            ripper.Dispose();
            user_event.Dispose();
            OnFinished();
            LogCore.Instance.PushError(Catalog.GetString("Cannot Import CD"), ripper.ErrorMessage); 
        }
        
        private void OnFinished()
        {
            drive.UnLockCD();
            EventHandler handler = Finished;
            if(handler != null) {
                handler(this, new EventArgs());
            }
        }
        
        private bool OnTimeout()
        {
            int diff = currentSeconds - lastPollSeconds;  
            lastPollSeconds = currentSeconds;
            
            if(diff <= 0) {
                user_event.Message = status;
                return true;
            }
            
            user_event.Message = status + String.Format(" ({0}x)", diff);
            user_event.OnUpdateStatus();
            return true;
        }
        
        private void OnRipperProgress(object o, AudioCdRipperProgressArgs args)
        {
            user_event.Header = "Importing CD...";
            user_event.Message = args.Track.Title;
            if(args.SecondsEncoded == 0) {
               
                return;
            }
            double totalprogress = (double)(args.SecondsEncoded + overallProgress) / (double)(totalCount);
            user_event.Progress = totalprogress;
            //LogCore.Instance.PushInformation("CD Ripping", string.Format("{0}% Total", user_event.Progress));
            currentSeconds = args.SecondsEncoded;
            user_event.OnUpdateStatus();
        }
        
        private void OnCancelRequested(object o, EventArgs args)
        {
            if(ripper != null) {
                ripper.Cancel();
                ripper.Dispose();
            }
            
            user_event.Dispose();
            OnFinished();
        }
        
        public int QueueSize {
            get {
                return total_tracks;
            }
        }
    }
}
